#include <string.h>

#include "CPU.h"
#include "base/Simulator.h"
#include "base/signals/SigContext.h"
#include "base/signals/SigHandler.h"
#include "base/signals/SigManager.h"
#include "mem/CancelRequestsRequest.h"

Pool* SigContext::pool = 
    NEW Pool("SigContext",                            
             sizeof(SigContext),                     
             100,
             Pool::CACHE_LINE_PADDING,                                     
             false);                                

SigContext::SigContext (int signum, void* extra, size_t extraSize) {
    context.uc_link = &resumeCtx;
    sigreturnCtx    = &context;
    this->signum    = signum;
    this->extra     = extra;
    this->extraSize = extraSize;
}

SigContext::SigContext (int signum, VA badAddr) {
    context.uc_link = &resumeCtx;
    sigreturnCtx    = &context;
    this->signum    = signum;
    this->extra     = NULL;
    this->extraSize = 0;
    info.si_addr = ADDR_VOIDSTAR(badAddr);    
}

SigContext::SigContext (siginfo_t* oinfo, ucontext_t* ocontext) {
    context.uc_link = &resumeCtx;

    sigreturnCtx = &context;
    memcpy(&info,oinfo,sizeof(siginfo_t));
    signum = info.si_signo;
    extra  = NULL;

    mcontext_t* dest  = &context.uc_mcontext;
    mcontext_t* src   = &ocontext->uc_mcontext;
    int mcontext_size = sizeof(mcontext_t);
    memcpy(&context, ocontext, sizeof(ucontext_t));

    memcpy(dest, src, mcontext_size);

    context.uc_mcontext = *dest;

    if (ocontext->uc_link != NULL) {
        dest = &resumeCtx.uc_mcontext;
        src  = &ocontext->uc_link->uc_mcontext;
        memcpy(&resumeCtx, ocontext->uc_link, sizeof(ucontext_t));
        memcpy(dest, src, mcontext_size);
        resumeCtx.uc_mcontext = *dest;
        context.uc_link = &resumeCtx;
    }
}

SigContext::~SigContext () {
}

void SigContext::updateParameters (CPU* runningcpu)
{
    if (signum == SIGILL || signum == SIGFPE) {
        // si_addr is supposed to contain the "bad address". In the
        // case of these signals it should be the PC so we update it
        // here from the real to the simulated PC. In the case of
        // SIGSEGV and SIGBUS, it is already correctly the bad
        // effective address.
        info.si_addr = ADDR_VOIDSTAR(runningcpu->getPC());
    }

    context.uc_mcontext.gregs[REG_GS]     = runningcpu->InterpCtxt.SegTable[GS].base;
    context.uc_mcontext.gregs[REG_FS]     = runningcpu->InterpCtxt.SegTable[FS].base;
    context.uc_mcontext.gregs[REG_ES]     = runningcpu->InterpCtxt.SegTable[ES].base;
    context.uc_mcontext.gregs[REG_DS]     = runningcpu->InterpCtxt.SegTable[DS].base;
    context.uc_mcontext.gregs[REG_EDI]    = runningcpu->InterpCtxt.GPR[EDI];
    context.uc_mcontext.gregs[REG_ESI]    = runningcpu->InterpCtxt.GPR[ESI];
    context.uc_mcontext.gregs[REG_EBP]    = runningcpu->InterpCtxt.GPR[EBP];
    context.uc_mcontext.gregs[REG_ESP]    = runningcpu->InterpCtxt.GPR[ESP];
    context.uc_mcontext.gregs[REG_EBX]    = runningcpu->InterpCtxt.GPR[EBX];
    context.uc_mcontext.gregs[REG_EDX]    = runningcpu->InterpCtxt.GPR[EDX];
    context.uc_mcontext.gregs[REG_ECX]    = runningcpu->InterpCtxt.GPR[ECX];
    context.uc_mcontext.gregs[REG_EAX]    = runningcpu->InterpCtxt.GPR[EAX];
    context.uc_mcontext.gregs[REG_EIP]    = runningcpu->InterpCtxt.IP;
    context.uc_mcontext.gregs[REG_CS]     = runningcpu->InterpCtxt.SegTable[CS].base;
    context.uc_mcontext.gregs[REG_EFL]    = runningcpu->getEFlags();
    context.uc_mcontext.gregs[REG_SS]     = runningcpu->InterpCtxt.SegTable[SS].base;

    if (context.uc_mcontext.fpregs) {
        for (int i = 0; i < X86_NUM_FPR; i++) {
            context.uc_mcontext.fpregs->_st[i].significand[0] = runningcpu->InterpCtxt.FPR[i].low >> 48 & 0xffff;
            context.uc_mcontext.fpregs->_st[i].significand[1] = runningcpu->InterpCtxt.FPR[i].low >> 32 & 0xffff;
            context.uc_mcontext.fpregs->_st[i].significand[2] = runningcpu->InterpCtxt.FPR[i].low >> 16 & 0xffff;
            context.uc_mcontext.fpregs->_st[i].significand[3] = runningcpu->InterpCtxt.FPR[i].low >>  0 & 0xffff;
            context.uc_mcontext.fpregs->_st[i].exponent       = runningcpu->InterpCtxt.FPR[i].high;
        }
    }

    // dar has the EA of the failed memory accress.
    // dsisr has more information about the memory access performed.
    // ignore MQ register (601 only) 
    // for now, the registered user sig_stack is ignored
    // setup the stack for user handlers in the simulation mode
    context.uc_stack.ss_flags |= SA_ONSTACK; // we will have application code use this stack
}

void SigContext::switchToSigHandling (CPU* runningcpu, Checkpoint* savedcpu, Byte* stack_aligned_end)
{
    signalLog->i("======== going into sig handler ===========================\n");
    signalLog->i("[SigContext] IP   : "RegFormat"\n", runningcpu->InterpCtxt.IP);
    signalLog->i("[SigContext] EDI  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[EDI]);
    signalLog->i("[SigContext] ESI  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[ESI]);
    signalLog->i("[SigContext] EBP  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[EBP]);
    signalLog->i("[SigContext] ESP  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[ESP]);
    signalLog->i("[SigContext] EBX  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[EBX]);
    signalLog->i("[SigContext] EDX  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[EDX]);
    signalLog->i("[SigContext] ECX  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[ECX]);
    signalLog->i("[SigContext] EAX  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[EAX]);
    signalLog->i("=================================================\n");
    
    // first we want to update the context given to us from the OS with the simulated values in the CPU
    updateParameters(runningcpu);

    // only registers need to be backed up correctly. the other values in this structure are not used later
    runningcpu->takeCheckpoint(savedcpu);

    // set up the registers so that CPU will think that it jumps to the user handlers directly
    // without issuing the instruction again.  In case the user returns from the signal handler
    // we set the return value to SigHandler::afterSignalHandler, which will perform the
    // SIGEXIT ISA.
    runningcpu->setupStack(stack_aligned_end, 
                           (Reg)ADDR_UINT64(global_sim->getSM()->getHandler(signum)),
                           (uint32)SigHandler::afterSignalHandler,
                           4,
                           signum,
                           ADDR_UINT64(&info),
                           ADDR_UINT64(&context),
                           ADDR_UINT64(extra));
    ASSERT(runningcpu->getPC() != 0);

    // cancel MemoryRequest flighted in normal execution context
    if (runningcpu->P->cancelDataRequest()) {
        CancelRequestsRequest* cr =
            NEW CancelRequestsRequest(runningcpu->P, global_sim->getMemory());
        runningcpu->P->routePacket(cr);
        delete cr;
    }

    invalidateCaches(runningcpu);
}

void SigContext::invalidateCaches(CPU* runningcpu)
{
    // invalidate memory under info and context to make sure app sees values set by simulator...
    global_sim->invalidateCaches(runningcpu->P, "siginfo_t",  ADDR_UINT64(&info),    sizeof(siginfo_t));
    global_sim->invalidateCaches(runningcpu->P, "ucontext_t", ADDR_UINT64(&context), sizeof(ucontext_t));
    if (extra != NULL) {
        global_sim->invalidateCaches(runningcpu->P, "extra",  ADDR_UINT64(extra),    extraSize);
    }
}

void SigContext::switchToNormalExec (CPU* runningcpu) {
    // restore the status of runningcpu(i.e. curPE)
    // from ctx passed by the user's sig handler.
    // most register values may not be changed by the user's sig handlers.
    ucontext_t* ctx = sigreturnCtx;

    // invalidate memory under info and context which wll be deallocated...
    runningcpu->P->rawFlushCaches(                            ADDR_UINT64(&info),    sizeof(siginfo_t));
    global_sim->invalidateCaches(runningcpu->P, "siginfo_t",  ADDR_UINT64(&info),    sizeof(siginfo_t));
    runningcpu->P->rawFlushCaches(                            ADDR_UINT64(&context), sizeof(ucontext_t));
    global_sim->invalidateCaches(runningcpu->P, "ucontext_t", ADDR_UINT64(&context), sizeof(ucontext_t));

    runningcpu->InterpCtxt.SegTable[GS].base = (uint32)ctx->uc_mcontext.gregs[REG_GS];
    runningcpu->InterpCtxt.SegTable[FS].base = (uint32)ctx->uc_mcontext.gregs[REG_FS];
    runningcpu->InterpCtxt.SegTable[ES].base = (uint32)ctx->uc_mcontext.gregs[REG_ES];
    runningcpu->InterpCtxt.SegTable[DS].base = (uint32)ctx->uc_mcontext.gregs[REG_DS];
    runningcpu->InterpCtxt.GPR[EDI]          = (uint32)ctx->uc_mcontext.gregs[REG_EDI];
    runningcpu->InterpCtxt.GPR[ESI]          = (uint32)ctx->uc_mcontext.gregs[REG_ESI];
    runningcpu->InterpCtxt.GPR[EBP]          = (uint32)ctx->uc_mcontext.gregs[REG_EBP];
    runningcpu->InterpCtxt.GPR[ESP]          = (uint32)ctx->uc_mcontext.gregs[REG_ESP];
    runningcpu->InterpCtxt.GPR[EBX]          = (uint32)ctx->uc_mcontext.gregs[REG_EBX];
    runningcpu->InterpCtxt.GPR[EDX]          = (uint32)ctx->uc_mcontext.gregs[REG_EDX];
    runningcpu->InterpCtxt.GPR[ECX]          = (uint32)ctx->uc_mcontext.gregs[REG_ECX];
    runningcpu->InterpCtxt.GPR[EAX]          = (uint32)ctx->uc_mcontext.gregs[REG_EAX];
    runningcpu->InterpCtxt.IP                = (uint32)ctx->uc_mcontext.gregs[REG_EIP];
    runningcpu->InterpCtxt.SegTable[CS].base = (uint32)ctx->uc_mcontext.gregs[REG_CS];
    runningcpu->setEFlags((IVAL)ctx->uc_mcontext.gregs[REG_EFL]);
    runningcpu->InterpCtxt.SegTable[SS].base = (uint32)ctx->uc_mcontext.gregs[REG_SS];

    if (ctx->uc_mcontext.fpregs) {
        for (int i = 0; i < X86_NUM_FPR; i++) {
            runningcpu->InterpCtxt.FPR[i].low = 0;
            runningcpu->InterpCtxt.FPR[i].low |= ((unsigned long long)ctx->uc_mcontext.fpregs->_st[i].significand[0] << 48) && 0xffff000000000000ULL;
            runningcpu->InterpCtxt.FPR[i].low |= ((unsigned long long)ctx->uc_mcontext.fpregs->_st[i].significand[1] << 32) && 0x0000ffff00000000ULL;
            runningcpu->InterpCtxt.FPR[i].low |= ((unsigned long long)ctx->uc_mcontext.fpregs->_st[i].significand[2] << 16) && 0x00000000ffff0000ULL;
            runningcpu->InterpCtxt.FPR[i].low |= ((unsigned long long)ctx->uc_mcontext.fpregs->_st[i].significand[3] <<  0) && 0x000000000000ffffULL;
            runningcpu->InterpCtxt.FPR[i].high = ctx->uc_mcontext.fpregs->_st[i].exponent;
        }
    }
    signalLog->i("======== coming out of sig handler ========================\n");
    signalLog->i("[SigContext] IP   : "RegFormat"\n", runningcpu->InterpCtxt.IP);
    signalLog->i("[SigContext] EDI  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[EDI]);
    signalLog->i("[SigContext] ESI  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[ESI]);
    signalLog->i("[SigContext] EBP  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[EBP]);
    signalLog->i("[SigContext] ESP  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[ESP]);
    signalLog->i("[SigContext] EBX  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[EBX]);
    signalLog->i("[SigContext] EDX  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[EDX]);
    signalLog->i("[SigContext] ECX  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[ECX]);
    signalLog->i("[SigContext] EAX  : "RegFormat"\n", runningcpu->InterpCtxt.GPR[EAX]);
    signalLog->i("=================================================\n");
}

long unsigned int SigContext::getSigIP(ucontext_t* context) {
    return context->uc_mcontext.gregs[REG_EIP];
}
